#include "OpenglAppFramework/inc/PlugIn/Mesh.h"
#include "OpenglAppFramework/inc/Config.h"
#include "glad/inc/glad.h"
#include "glfw/inc/glfw3.h"
#include <iostream>
#include <sstream>

Mesh::Mesh(const std::string& name):
    PlugIn(name, PlugInType::Mesh) {
    m_primit_cnt = m_primit_vtx_cnt = m_vtx_cnt = 0;
    m_vtx_cords = m_vtx_norm = m_vtx_tex_cords = nullptr;
    m_vtx_cords_offset = m_vtx_norm_offset = m_vtx_tex_cords_offset = 0;
    m_vtx_cords_size = m_vtx_norm_size = m_vtx_tex_cords_size = 0;
    m_primit_vtx_idxs = nullptr;
    m_primit_vtx_idxs_size = 0;
    m_vao_id = m_vbo_id = m_ebo_id = 0;
    m_vtx_attr_idx = 0;
    m_vbo_size = 0;
}

Mesh::~Mesh() {
    m_obj_loader.unload();
}

int Mesh::setConfigData(const nlohmann::json& data) {
    // data内容：{obj_dir: xxx, obj_file_name: xxx}
    m_obj_dir = OpenglAppConfig::resource_dir + '/' + data["obj_dir"].get<std::string>();
    m_obj_file_name = data["obj_file_name"].get<std::string>();
    return 0;
}

int Mesh::loadData() {
    int ret = m_obj_loader.load(m_obj_dir, m_obj_file_name);
    if(ret != 0) {
        return -1;
    }
    m_vtx_cords = m_obj_loader.location();
    m_vtx_norm = m_obj_loader.normal();
    m_vtx_tex_cords = m_obj_loader.texture_cord();
    m_vtx_cords_offset = m_vtx_norm_offset = m_vtx_tex_cords_offset = 0;
    m_vtx_cords_size = m_obj_loader.primitVertexCount() * 3 * sizeof(float);
    m_vtx_norm_size = m_obj_loader.primitVertexCount() * 3 * sizeof(float);
    m_vtx_tex_cords_size = m_obj_loader.primitVertexCount() * 2 * sizeof(float);

    m_primit_vtx_idxs = nullptr;
    m_primit_vtx_idxs_size = 0;

    m_primit_cnt = (size_t)(m_obj_loader.primitCount());
    m_primit_vtx_cnt = (size_t)(m_obj_loader.primitVertexCount());
    m_vtx_cnt = m_primit_vtx_cnt;

    return 0;
}

int Mesh::init() {
    if(loadData() != 0) {
        return -1;
    }
    
    // 计算各个属性的偏移量
    uint32_t vbo_offset = 0;
    m_vbo_size = 0;
    m_vtx_cords_offset = m_vbo_size;
    m_vbo_size += m_vtx_cords_size;
    m_vtx_norm_offset = m_vbo_size;
    m_vbo_size += m_vtx_norm_size;
    m_vtx_tex_cords_offset = m_vbo_size;
    m_vbo_size += m_vtx_tex_cords_size;

    if(m_primit_vtx_idxs_size % (sizeof(float) * 3) != 0
    || m_vtx_cords_size % (sizeof(float) * 3) != 0) {
        std::cerr << "illagle vertex attr data layout!" << std::endl;
        return -1;
    }
    if(m_primit_vtx_idxs == nullptr) {
        // ???
    }
    else {
        m_primit_cnt = m_primit_vtx_idxs_size / sizeof(float) / 3;
        m_primit_vtx_cnt = m_primit_vtx_idxs_size / sizeof(float);
        m_vtx_cnt = m_vtx_cords_size / sizeof(float) / 3;
    }

    // 创建顶点属性
    glGenVertexArrays(1, &m_vao_id);
    glGenBuffers(1, &m_vbo_id);
    glGenBuffers(1, &m_ebo_id);
    glBindVertexArray(m_vao_id);
    glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ebo_id);

    glBufferData(GL_ARRAY_BUFFER, m_vbo_size, nullptr, GL_STATIC_DRAW);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_primit_vtx_idxs_size, m_primit_vtx_idxs, GL_STATIC_DRAW);

    // 顶点坐标
    uint32_t attr_idx = getNextAttrIndex();
    glBufferSubData(GL_ARRAY_BUFFER, m_vtx_cords_offset, m_vtx_cords_size, m_vtx_cords);
    glEnableVertexAttribArray(attr_idx);
    glVertexAttribPointer(attr_idx, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), reinterpret_cast<void*>(m_vtx_cords_offset));
    // 顶点法向量
    if(m_vtx_norm != nullptr) {
        attr_idx = getNextAttrIndex();
        glBufferSubData(GL_ARRAY_BUFFER, m_vtx_norm_offset, m_vtx_norm_size, m_vtx_norm);
        glEnableVertexAttribArray(attr_idx);
        glVertexAttribPointer(attr_idx, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), reinterpret_cast<void*>(m_vtx_norm_offset));
    }
    // 贴图坐标
    if(m_vtx_tex_cords) {
        attr_idx = getNextAttrIndex();
        glBufferSubData(GL_ARRAY_BUFFER, m_vtx_tex_cords_offset, m_vtx_tex_cords_size, m_vtx_tex_cords);
        glEnableVertexAttribArray(attr_idx);
        glVertexAttribPointer(attr_idx, 2, GL_FLOAT, GL_FALSE, 2*sizeof(float), reinterpret_cast<void*>(m_vtx_tex_cords_offset));
    }
    return 0;
}

void Mesh::destroy() {
    glDeleteVertexArrays(1, &m_vao_id);
    glDeleteBuffers(1, &m_vbo_id);
    glDeleteBuffers(1, &m_ebo_id);
}


uint32_t Mesh::getVAOId() const {
    return m_vao_id;
}

uint32_t Mesh::getPrimitVertexCount() const {
    return m_primit_vtx_cnt;
}

bool Mesh::hasIndexBuffer() const {
    return m_primit_vtx_idxs != nullptr;
}

uint32_t Mesh::getNextAttrIndex() {
    return m_vtx_attr_idx++;
}

std::string Mesh::info() const {
    std::stringstream ss;
    ss << "prmt_cnt:" << m_primit_cnt << " vtx_cnt:" << m_vtx_cnt << " prmt_vtx_cnt:" << m_primit_vtx_cnt << std::endl
        << "cord/norm/tcord offset: " << m_vtx_cords_offset << ' ' << m_vtx_norm_offset << ' ' << m_vtx_tex_cords_offset << std::endl
        << "cord/norm/tcord/idx size: " << m_vtx_cords_size << ' ' << m_vtx_norm_size << ' ' << m_vtx_tex_cords_size << ' ' << m_primit_vtx_idxs_size << std::endl
        << "cord/norm/tcord/idx addr: " << m_vtx_cords << ' ' << m_vtx_norm << ' ' << m_vtx_tex_cords << ' ' << m_primit_vtx_idxs << std::endl
        << "vao:" << m_vao_id << " vbo:" << m_vbo_id << " ebo:" << m_ebo_id << " next_attr_idx:" << m_vtx_attr_idx << std::endl
        << "vbo_size:" << m_vbo_size << std::endl;

    return ss.str();
}
